package umg.os.timer;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 
 * @author 
 * <a href = "mailto:ordiel1910@gmail.com">
 * Cesar Alonso Martinez Sanchez</a>
 *
 */
public class Timer implements Runnable {
	
	private static Timer singletonInstance = new Timer();
	private List<TimerListener> listeners;
	private volatile long timeUnitDuration;
	private final AtomicBoolean running = new AtomicBoolean(false);
	private long currentCycle;
	
	private Timer(){
		listeners = new ArrayList<TimerListener>();
		timeUnitDuration = 250;
		currentCycle = 0;
	}
	
	public static void setTimeUnitDuration(long timeUnitDuration) {
		singletonInstance.timeUnitDuration = timeUnitDuration;
	}
	
	/**
	 * Adds {@link #listener} to the list of objects interested in the events of this class.
	 * 
	 * @param listener
	 * The object interested in listening the events of this class.
	 */
	public static void register(TimerListener listener) {
		synchronized (singletonInstance.listeners) {
			if (listener == null) {
				throw new NullPointerException("A listener cannot be null");
			}
			if (!singletonInstance.listeners.contains(listener)) {
				singletonInstance.listeners.add(listener);
			}
		}
	}
	
	/**
	 * Removes the specified object from the list of listeners of this class.
	 * <p>
	 * It is very important to call this method whenever an object is no longer
	 * necessary or it is no longer required that object to listen the events of 
	 * this class, first of all to allow the garbage collector to collect such 
	 * object, and to prevent that object to keep interacting with other objects
	 * in an inappropriate way.
	 * 
	 * @param listener
	 * The object which no longer require to listen the events of this class.
	 */
	public static void unregister(TimerListener listener) {
		synchronized (singletonInstance.listeners) {
			singletonInstance.listeners.remove(listener);
		}
	}
	
	/**
	 * Finish safely the execution of
	 * this timer, once a timer has been stopped
	 * it cannot be started again.
	 * 
	 *  <i>Note:this method does not finish immediately the execution of this timer,
	 *  if this object is notifying while this method is call, the notification process 
	 *  will also be interrupted, so not all the listeners may receive the last message</i>
	 */
	public static void stop() {
		singletonInstance.running.set(false);
	}
	
	public static void start() {
		new Thread(singletonInstance).start();
	}
	
	public static long getCurrentCycle() {
		return singletonInstance.currentCycle;
	}
	
	@Override
	public void run() {
		running.set(true);
		long nextTimeUnitChange = System.currentTimeMillis() + timeUnitDuration;
		while (running.get()) {
			if (System.currentTimeMillis() >= nextTimeUnitChange) {
				nextTimeUnitChange += timeUnitDuration;
				notifyTimeUnitChange();
			}
		}
		
	}
	
	/**
	 * Call the method {@link TimerListener#nextTimeUnit()} for each element 
	 * in {@link #listeners}, if the method {@link #stop()} is called while 
	 * this method is in progress, will finish the notification in progress 
	 * and then it will notify no other objects after that.
	 */
	private void notifyTimeUnitChange() {
		currentCycle++;
		synchronized (listeners) {
			for (TimerListener listener : listeners) {
				if (!running.get()) {
					break;
				}
				listener.nextTimeUnit();
				Thread.yield();
			}
		} 
	}

	/**
	 * 
	 * @author
	 *  <a href = "mailto:ordiel1910@gmail.com">
	 * Cesar Alonso Martinez Sanchez</a>
	 *
	 */
	public interface TimerListener {
		
		/**
		 * The implementing class should determine which is the appropriate
		 * behavior for this event.  
		 */
		void nextTimeUnit();
		
	}	

}
